package com.motu.vertx.module.utility.toolset;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

/**
 * 预加载工具
 * <p>访问类时会先加载，如果方法访问的类多或类使用了较多的函数式方法，会导致第一次执行方法比较耗时
 * <p>预加载，就是在程序启动时提前访问一次类，使得后续无需花费加载时间
 *
 * @author 黄贤玮
 * create date: 2025/4/18 星期五
 */
public class PreLoaderUtils {
	private final static Logger logger = LoggerFactory.getLogger(PreLoaderUtils.class);

	private static final int DEPTH_MAX = 4;// 限制包遍历深度，避免传入错误的包名时遍历过多

	/**
	 * （批量）扫描和预加载指定包下的类，并控制遍历深度
	 *
	 * @param packageName 基础包名（可使用Demo.class.getPackage().getName()获取包名）
	 * @param depth 遍历深度:
	 * <p>0 - 只遍历目标包中的类，不遍历子包
	 * <p>1 - 遍历目标包和其直接子包中的类
	 * <p>n - 遍历到第n层子包(为了避免潜在的问题，默认最大4)
	 * @param ignorePattern 符合条件的将被忽略。传null不执行判断
	 * <p>
	 * 例1：Pattern.compile("test", Pattern.CASE_INSENSITIVE);，
	 * 忽略名称中有test的类，如org.test.Demo和org.data.TestData
	 * <p>
	 * 例2:Pattern.compile("(\\.userdata\\.|\\.commondata\\.)");，
	 * 忽略名称中有.userdata.或.commondata.的类，适合用来忽略指定的包
	 * 如com.test.game.commondata.Item和com.test.game.userdata.UserItem
	 */
	public static void preLoadClassWithDepth(String packageName, int depth,
											 Pattern ignorePattern) throws Exception {
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

		// 替换包名中的点为路径分隔符
		String basePackagePath = ClassUtils.convertClassNameToResourcePath(packageName);

		// 根据深度构建搜索路径
		List<String> packageSearchPathList = new ArrayList<>();
		if (depth >= 0 && depth < DEPTH_MAX) {
			// 构建一个特定深度的路径模式
			StringBuilder patternBuilder = new StringBuilder(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)
					.append(basePackagePath);
			// 添加深度对应的目录层级
			for (int i = 0; i <= depth; i++) {
				patternBuilder.append("/*");
				packageSearchPathList.add(patternBuilder + ".class");
			}
		} else {
			throw new IllegalArgumentException("depth limit [0," + DEPTH_MAX + "], input:" + depth);
		}
		long startTime = System.currentTimeMillis();
		int count = 0;
		for (String packageSearchPath : packageSearchPathList) {
			// 获取所有匹配的资源
			Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
			for (Resource resource : resources) {
				try {
					MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
					String className = metadataReader.getClassMetadata().getClassName();
					// 使用正则表达式进行过滤
					if (ignorePattern != null && ignorePattern.matcher(className).find()) {
						continue;
					}
					count++;
					Class.forName(className);
				} catch (Exception e) {
					logger.error("error file name: {}, Exception: {}", resource.getFilename(), Tool.getException(e));
				}
			}
		}
		logger.info("preLoadClass num:{} time:{}ms", count, System.currentTimeMillis() - startTime);
	}

	/**
	 * （批量）扫描和预加载指定包下的类，并控制遍历深度
	 * <p>配置类（commondata包中的）一般启动的时候会初始化，不需要预加载，建议过滤掉
	 *
	 * <p>！！！因为Class.forName会造成阻塞，在Vertx需要使用executeBlocking！！！
	 *
	 * @param ordered 见{@link Vertx#executeBlocking(Handler, boolean, Handler)}
	 * @param packageName 基础包名（可使用Demo.class.getPackage().getName()获取包名）
	 * @param depth 遍历深度:
	 * <p>0 - 只遍历目标包中的类，不遍历子包
	 * <p>1 - 遍历目标包和其直接子包中的类
	 * <p>n - 遍历到第n层子包(为了避免潜在的问题，默认最大4)
	 * @param ignorePattern 符合条件的将被忽略。传null不执行判断。
	 * <p>
	 * 例1：Pattern.compile("test", Pattern.CASE_INSENSITIVE);，
	 * 忽略名称中有test的类，如org.test.Demo和org.data.TestData
	 * <p>
	 * 例2:Pattern.compile("(\\.userdata\\.|\\.commondata\\.)");，
	 * 忽略名称中有.userdata.或.commondata.的类，适合用来忽略指定的包
	 * 如com.test.game.commondata.Item和com.test.game.userdata.UserItem
	 */
	public static void preLoadClassWithDepth(Vertx vertx, boolean ordered, String packageName, int depth,
											 Pattern ignorePattern) {
		vertx.executeBlocking(blockPromise -> {
			// 调用阻塞的、需要消耗显著执行时间的API
			try {
				preLoadClassWithDepth(packageName, depth, ignorePattern);
				blockPromise.complete(true);
			} catch (Exception e) {
				logger.error(Tool.getException(e));
				blockPromise.complete(false);
			}
		}, ordered, res -> {
			logger.info("preLoadClassWithDepth result is: {}", res.result());
		});
	}

	/**
	 * 通过类名称预加载指定类
	 * <p>只需预加载个别类适用
	 *
	 * @param classNames 完整类名数组
	 */
	public static void preLoadClassesByName(String... classNames) {
		int length = classNames.length;
		long time = System.currentTimeMillis();
		for (String className : classNames) {
			try {
				Class.forName(className);
			} catch (ClassNotFoundException e) {
				logger.error("preLoadClasses className:{}, error:{}", className, Tool.getException(e));
			}
		}
		logger.info("preLoadClasses num:{}, time:{}ms", length, (System.currentTimeMillis() - time));
	}

	/**
	 * 通过类名称预加载类
	 * <p>只需预加载个别类适用
	 * <p>！！！因为Class.forName会造成阻塞，在Vertx需要使用executeBlocking！！！
	 *
	 * @param ordered 见{@link Vertx#executeBlocking(Handler, boolean, Handler)}
	 */
	public static void preLoadClasses(Vertx vertx, boolean ordered, String... classNames) {
		vertx.executeBlocking(blockPromise -> {
			// 调用阻塞的、需要消耗显著执行时间的API
			try {
				preLoadClassesByName(classNames);
				blockPromise.complete(true);
			} catch (Exception e) {
				logger.error(Tool.getException(e));
				blockPromise.complete(false);
			}
		}, ordered, res -> {
			logger.info("preLoadClasses class name array result is: {}", res.result());
		});
	}

}
